
#include <string.h>
#include <stdint.h>

#include "spi.h"
#include "norflash.h"

static inline uint8_t _rwByte(uint8_t cmd)
{
  uint8_t val;
  HAL_SPI_TransmitReceive(&hspi2, &cmd, &val, 1, HAL_MAX_DELAY);
  return val;
}

static inline void _wByte(uint8_t cmd)
{
  HAL_SPI_Transmit(&hspi2, &cmd, 1, HAL_MAX_DELAY);
}

static inline uint8_t _rByte()
{
  uint8_t val;
  HAL_SPI_Receive(&hspi2, &val, 1, HAL_MAX_DELAY);
  return val;
}

static inline void _wNcs(uint8_t val)
{
  HAL_GPIO_WritePin(NOR_NCS_GPIO_Port, NOR_NCS_Pin, (GPIO_PinState)val);
}

static inline uint8_t NORFLASH_ReadStatusReg(uint8_t reg)
{
  uint8_t cmd;
  uint8_t val;

  switch (reg)
  {
  case 1:
      cmd = 0x5;
      break;

  case 2:
      cmd = 0x35;
      break;

  case 3:
      cmd = 0x15;
      break;

  default:
      cmd = 0x5;
      break;
  }

  _wNcs(NORFLASH_Enable);
  _wByte(cmd);
  val = _rByte();
  _wNcs(NORFLASH_Disable);

  return val;
}

static inline void NORFLASH_WaitWriteEnd(void)
{
  while ((NORFLASH_ReadStatusReg(1) & 0x01) == 0x01) ;
}

static void NORFLASH_WriteEnable(void)
{
  _wNcs(NORFLASH_Enable);
  _wByte(NORFLASH_WRITE_ENABLE);
  _wNcs(NORFLASH_Disable);
}

void NORFLASH_Reset(void)
{
  _wNcs(NORFLASH_Enable);
  _wByte(NORFLASH_ENABLE_RESET);
  _wNcs(NORFLASH_Disable);

  _wNcs(NORFLASH_Enable);
  _wByte(NORFLASH_ENABLE_RESET);
  _wNcs(NORFLASH_Disable);
}

void NORFLASH_DeviceID(uint8_t *v)
{
  _wNcs(NORFLASH_Enable);

  _wByte(NORFLASH_DEVICE_ID);
  _wByte(0);
  _wByte(0);
  _wByte(0);

  v[0] = _rByte();
  v[1] = _rByte();
  v[2] = _rByte();

  _wNcs(NORFLASH_Disable);
}

int NORFLASH_EraseSector(uint32_t SectorAddress)
{
  uint8_t cmd[4];

  cmd[0] = NORFLASH_ERASE_SECTOR;
  cmd[1] = (SectorAddress & 0xff0000) >> 16;
  cmd[2] = (SectorAddress & 0xff00) >> 8;
  cmd[3] = SectorAddress & 0xff;

  NORFLASH_WriteEnable();

  _wNcs(NORFLASH_Enable);

  _wByte(cmd[0]);
  _wByte(cmd[1]);
  _wByte(cmd[2]);
  _wByte(cmd[3]);

  _wNcs(NORFLASH_Disable);

  NORFLASH_WaitWriteEnd();

  return NORFLASH_OK;
}

int NORFLASH_EraseBlock(uint32_t BlackAddress)
{
  uint8_t cmd[4];

  cmd[0] = NORFLASH_ERASE_BLOCK;
  cmd[1] = (BlackAddress & 0xff0000) >> 16;
  cmd[2] = (BlackAddress & 0xff00) >> 8;
  cmd[3] = BlackAddress & 0xff;

  NORFLASH_WriteEnable();

  _wNcs(NORFLASH_Enable);

  _wByte(cmd[0]);
  _wByte(cmd[1]);
  _wByte(cmd[2]);
  _wByte(cmd[3]);

  _wNcs(NORFLASH_Disable);

  NORFLASH_WaitWriteEnd();

  return NORFLASH_OK;
}

int NORFLASH_EraseChip(void)
{
  NORFLASH_WriteEnable();

  _wNcs(NORFLASH_Enable);
  _wByte(NORFLASH_ERASE_CHIP);
  _wNcs(NORFLASH_Disable);

  NORFLASH_WaitWriteEnd();

  return NORFLASH_OK;
}

static int _NORFLASH_Read(uint8_t *pData, uint32_t ReadAddr, uint32_t Size)
{
  uint8_t cmd[4] = {0x0};

  cmd[0] = NORFLASH_READ;
  cmd[1] = (ReadAddr & 0xff0000) >> 16;
  cmd[2] = (ReadAddr & 0xff00) >> 8;
  cmd[3] = ReadAddr & 0xff;

  _wNcs(NORFLASH_Enable);

  if (Size > NORFLASH_PAGE_SIZE)
  {
      Size = NORFLASH_PAGE_SIZE;
  }

  _wByte(cmd[0]);
  _wByte(cmd[1]);
  _wByte(cmd[2]);
  _wByte(cmd[3]);

  for (volatile int i = 0; i < Size; i++)
  {
      pData[i] = _rByte();
  }

  _wNcs(NORFLASH_Disable);

  NORFLASH_WaitWriteEnd();
  return NORFLASH_OK;
}

int NORFLASH_Read256B(uint8_t *pData, uint32_t ReadAddr)
{
  uint8_t cmd[4] = {0x0};

  cmd[0] = NORFLASH_READ;
  cmd[1] = (ReadAddr & 0xff0000) >> 16;
  cmd[2] = (ReadAddr & 0xff00) >> 8;
  cmd[3] = ReadAddr & 0xff;

  _wNcs(NORFLASH_Enable);

  _wByte(cmd[0]);
  _wByte(cmd[1]);
  _wByte(cmd[2]);
  _wByte(cmd[3]);

  for (volatile int i = 0; i < NORFLASH_PAGE_SIZE; i++)
  {
      pData[i] = _rByte();
  }

  //while ((_rwByte(0x5) & 0x01) == 0x01) ;

  _wNcs(NORFLASH_Disable);
  NORFLASH_WaitWriteEnd();
  return NORFLASH_OK;
}

int NORFLASH_Read(uint8_t *pData, uint32_t ReadAddr, uint32_t Size)
{
  int ret = NORFLASH_OK;
  uint32_t end_addr, current_size, current_addr;
  uint8_t *read_data;

  current_size = NORFLASH_PAGE_SIZE - (ReadAddr % NORFLASH_PAGE_SIZE);

  if (current_size > NORFLASH_PAGE_SIZE)
  {
    current_size = NORFLASH_PAGE_SIZE;
  }

  current_addr = ReadAddr;
  end_addr = ReadAddr + Size;
  read_data = pData;

  do
  {
    if (_NORFLASH_Read(read_data, current_addr, current_size) != NORFLASH_OK)
    {
      ret = NORFLASH_ERROR;
    }
    else
    {
      current_addr += current_size;
      read_data += current_size;
      current_size = ((current_addr + NORFLASH_PAGE_SIZE) > end_addr) ? (end_addr - current_addr) : NORFLASH_PAGE_SIZE;
    }
  } while ((current_addr < end_addr) && (ret == NORFLASH_OK));

  return ret;
}

int NORFLASH_PageProgram(uint8_t *pData, uint32_t WriteAddr, uint32_t Size)
{
  uint8_t cmd[4] = {0x0};

  cmd[0] = NORFLASH_PAGE_PROGRAM;
  cmd[1] = (WriteAddr & 0xff0000) >> 16;
  cmd[2] = (WriteAddr & 0xff00) >> 8;
  cmd[3] = WriteAddr & 0xff;

  NORFLASH_WriteEnable();

  _wNcs(NORFLASH_Enable);

  if (Size > NORFLASH_PAGE_SIZE)
  {
      Size = NORFLASH_PAGE_SIZE;
  }

  _wByte(cmd[0]);
  _wByte(cmd[1]);
  _wByte(cmd[2]);
  _wByte(cmd[3]);

  for (volatile int i = 0; i < Size; i++)
  {
    _wByte(pData[i]);
  }

  _wNcs(NORFLASH_Disable);

  NORFLASH_WaitWriteEnd();

  return NORFLASH_OK;
}

int NORFLASH_Write(uint8_t *pData, uint32_t WriteAddr, uint32_t Size)
{
  int ret = NORFLASH_OK;
  uint32_t end_addr, current_size, current_addr;
  uint8_t *write_data;

  current_size = NORFLASH_PAGE_SIZE - (WriteAddr % NORFLASH_PAGE_SIZE);

  if (current_size > NORFLASH_PAGE_SIZE)
  {
    current_size = NORFLASH_PAGE_SIZE;
  }

  current_addr = WriteAddr;
  end_addr = WriteAddr + Size;
  write_data = pData;

  do
  {
    if (NORFLASH_PageProgram(write_data, current_addr, current_size) != NORFLASH_OK)
    {
      ret = NORFLASH_ERROR;
    }
    else
    {
      current_addr += current_size;
      write_data += current_size;
      current_size = ((current_addr + NORFLASH_PAGE_SIZE) > end_addr) ? (end_addr - current_addr) : NORFLASH_PAGE_SIZE;
    }
  } while ((current_addr < end_addr) && (ret == NORFLASH_OK));

  return ret;
}
